#pragma once
#ifdef __ANDROID__

#include <android/asset_manager_jni.h>
#include "../../core/cbase.h"

class AAssetMgr {
public:
    static AAssetMgr* get() {
        static auto instance = new AAssetMgr;
        return instance;
    }
    
    /** @warning call it after Activity::onCreate(), so be cautious when using static initialization! */
    void init(JNIEnv* env, jobject assetManager, const char* internalPath, const char* externalPath) {
        m_assetMgr = AAssetManager_fromJava(env, assetManager);

        m_internalPath = internalPath;
        m_externalPath = externalPath;
    }

    /**@note if the `path` is in Android `assets` dir, `path` of course is not begin with `/` */
    static bool isValidAssetPath(const std::string& path) { return path.substr(0, 1) != "/"; }

    bool exists(const std::string& assetPath) {
        AAsset* asset = AAssetManager_open(m_assetMgr, assetPath.c_str(), AASSET_MODE_BUFFER);
        bool exists = asset != nullptr;
        AAsset_close(asset);
        return exists;
    }

    void readFileAsString(std::string& out, const std::string& assetPath) {
        CByteArray buffer;
        loadAsset(buffer, assetPath);
        out = std::string(buffer.begin(), buffer.end());
    }

    /** @param filterExtension like: .txt, .json, ... */
    void filesOfDir(std::vector<std::string>& outFileNames, const std::string& dirPath, const char* filterExtension, bool asc = true) {
        AAssetDir* assetDir = AAssetManager_openDir(m_assetMgr, dirPath.c_str());
        const char* filename;
        while ((filename = AAssetDir_getNextFileName(assetDir)) != nullptr) {
            std::string filePath = std::string(dirPath) + "/" + filename;
                std::string ext = std::string(filename).substr(std::string(filename).find_last_of('.'));
                if (ext == filterExtension)
                    outFileNames.push_back(filename);
        }
        AAssetDir_close(assetDir);

        std::sort(outFileNames.begin(), outFileNames.end(), [asc](const std::string& a, const std::string& b) {
            if (asc) return a.length() < b.length() || (a.length() == b.length() && a < b);
            else return a.length() > b.length() || (a.length() == b.length() && a > b);
        });
    }

    bool loadAsset(CByteArray& outBuffer, const std::string& assetPath) {
        int status = 0;
        return loadAsset(outBuffer, assetPath, &status);
    }

    bool loadAsset(CByteArray& outBuffer, const std::string& assetPath, int* status) {
        if (m_assetMgr == nullptr) {
            LOG_PRINT_E("AAssetMgr", "%s", "Asset manager not initialized!");
            *status = 100;
            std::abort();
        }

        AAsset* asset = AAssetManager_open(m_assetMgr, assetPath.c_str(), AASSET_MODE_BUFFER);
        if (asset == nullptr) {
            LOG_PRINT_E("AAssetMgr", "%s", "Asset manager open fail!");
            *status = 101;
            return false;
        }

        off_t fileSize = AAsset_getLength(asset);
        if (fileSize <= 0) {
            LOG_PRINT_E("AAssetMgr", "%s", "Asset manager getLength is 0!");
            AAsset_close(asset);
            *status = 102;
            return false;
        }

        outBuffer.resize(fileSize);

        int readBytes = AAsset_read(asset, outBuffer.data(), fileSize);
        AAsset_close(asset);

        if (readBytes != fileSize) {
            LOG_PRINT_E("AAssetMgr", "%s", "Asset manager read bytes not completed!");
            *status = 103;
            return false;
        }

        return true;
    }

private:
    AAssetMgr() = default;

    AAssetManager* m_assetMgr = nullptr;

    const char *m_internalPath, *m_externalPath;
};

#endif